package com.mikelouis.test.scala.impatient.chapter21_implicit

/**
 * Created by yuazhu on 5/13/2015.
 */
object Chapter21 extends App {
  // 1
  {
    //    implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
    //      @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
    //      def →[B](y: B): Tuple2[A, B] = ->(y)
    //    }
  }

  // 2
  {
    //    class MyRichInt(val intVal: Int) {
    //      def +%(percent: Int): Double = intVal * (1 + percent / 100.0)
    //
    //    }
    //    implicit def int2MyRichInt(input: Int) = new MyRichInt(input)
    //    println(120 +% 10)

    class MyRichVal[T <: AnyVal](val x: T) {
      def +%(percent: Int): T =
        math.round(this.x.asInstanceOf[Double] * (1 + percent / 100.0)).asInstanceOf[T]
    }

    object MyRichVal {
      implicit def anyVal2MyRichVal(input: AnyVal) = new MyRichVal(input)
    }

    import MyRichVal._
    println(120.0 +% 10)
  }

  // 3
  {
    class MyRichInt(val a: Int) {
      def ! = factorial(a)

      def !! = factTailRec(1, a)

      def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)

      def factTailRec(acc: Int, n: Int): Int = if (n == 0) acc else factTailRec(acc * n, n - 1)

    }

    implicit def int2MyRiichInt(input: Int) = new MyRichInt(input)

    println(4 !!)
    println(5 !)
  }

  // 4
  {
    abstract class TypeToObtain {
      type t
    }
    object aString extends TypeToObtain {
      type t = String
    }
    object anInt extends TypeToObtain {
      type t = Int
    }
    object aDouble extends TypeToObtain {
      type t = Double
    }

    class ReadDsl(val what: TypeToObtain) {
      def askingFor(ask: String): this.type = {
        print(ask + ": ")

        val value = what match {
          case `aString` => readLine()
          case `anInt` => readInt()
          case `aDouble` => readDouble()
        }
        println("You wrote " + value)

        this
      }

      def and(what: TypeToObtain) = Obtain(what)
    }

    def Obtain(what: TypeToObtain) = new ReadDsl(what)

    //    Obtain(aString) askingFor "Your name" and anInt askingFor "Your age" and aDouble askingFor "Your weight"
  }

  // 5
  {
    object Fraction {
      def apply(n: Int, d: Int) = new Fraction(n, d)

      def unapply(f: Fraction) = Some((f.num, f.den))
    }

    class Fraction(n: Int, d: Int) {

      import math._

      val num = if (d == 0) 1 else n * sign(d) / abs(gcd(n, d))
      val den = if (d == 0) 0 else d * sign(d) / abs(gcd(n, d))

      def sign(x: Int) = if (x > 0) 1 else if (x < 0) -1 else 0

      def gcd(x: Int, y: Int): Int = if (y == 0) x else gcd(y, x % y)

      def lcm(x: Int, y: Int) = x * y / gcd(x, y)

      override val toString = num + "/" + den
    }

    class RichFraction(n: Int, d: Int) extends Fraction(n, d) with Ordered[Fraction] {
      override def compare(that: Fraction): Int = this.num * that.den - this.den * that.num
    }

    implicit def fraction2RichFraction(fraction: Fraction) = new RichFraction(fraction.num, fraction.den)

    def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (order(a) < b) a else b

    println(smaller(Fraction(1, 7), Fraction(2, 9)))
  }

  // 6 & 7
  {
    import java.awt.Point
    class RichPointLex(xC: Int, yC: Int) extends Point(xC, yC) with Ordered[Point] {
      override def toString = "" +(x, y)

      def compare(that: Point) =
        toString.compare(new RichPointLex(that.x, that.y).toString)
    }

    //    implicit def point2RichPointLex(point: java.awt.Point) = new RichPointLex(point.x, point.y)
    implicit def point2Double(point: java.awt.Point): Double = math.sqrt(point.x * point.x + point.y * point.y)

    // In order to test comment point2Double from the package object.
    val p1 = new Point(1, 3)
    val p2 = new Point(1, 2)
    val p3 = new Point(2, 1)
    println(p1 > p2)
    println(p1 < p3)
    println(p2 > p3)
  }

  // 10
  {
    /**
     * Compile this with `scalac -Xprint:typer Exer10.scala` in order to find out which implicit conversion is used.
     * */
    object FindImplicitConversion extends App {
      "abc".map(_.toUpper)
      "abc".map(_.toInt)
    }
  }

}
